import { CanActivate, ExecutionContext, Injectable, Inject, UnauthorizedException } from '@nestjs/common';
import { Reflector } from "@nestjs/core";
import { Observable } from 'rxjs';
import { JwtService } from "@nestjs/jwt";

import { Request } from "express";

import { Permission } from "./user/entities/permission.entity";

interface JwtUserData {
  userId: number;
  username: string;
  email: string;
  roles: string[],
  permissions: Permission[]
}

// 同名的module和interface会合并，所以会和上面导入的 Request合并，这样就可以为引入的类型扩展属性了
declare module "express" {
  interface Request {
    user: JwtUserData
  }
}


/* 实现登录的权限校验 */

@Injectable()
export class LoginGuard implements CanActivate {

  @Inject(Reflector)
  private reflector: Reflector;

  @Inject(JwtService)
  private jwtService: JwtService;

  canActivate(
    context: ExecutionContext, // 这个上下文包含了 请求上下文 和 nest的Controller上下文
  ): boolean | Promise<boolean> | Observable<boolean> {
    // 获取拦截到的请求的请求体
    const request: Request = context.switchToHttp().getRequest();

    // 获取当前 controller，当前 handle下的require-login元数据（也可以称装饰器信息）
    const requireLogin = this.reflector.getAllAndOverride("require-login", [
      context.getClass(),
      context.getHandler()
    ]);
    // 不需要登录状态的接口直接放行
    if(!requireLogin) return true;
    // 需要登录的接口，再判断是否处于登录状态，从请求头中获取请求携带的token信息
    const authorization = request.headers.authorization;
    if(!authorization) throw new UnauthorizedException("用户未登录");

    // 如果用户携带了token信息，还需要判断token是否过期，是否正确
    try {
      // 一般是 Bearer 规范的token
      const token = authorization.split(" ")[1];
      const data = this.jwtService.verify<JwtUserData>(token);

      // 校验通过，将用户信息放到请求体中，方便后续接口角色权限校验permission使用
      request.user = {
        userId: data.userId,
        username: data.username,
        email: data.email,
        roles: data.roles,
        permissions: data.permissions
      }

      return true;
      
    } catch (error) {
      throw new UnauthorizedException("token失效，请重新登录");
    }


    return true;
  }
}
